//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<cmd_op.h>
///	@path	~/src/appear/app/
///	@date	2007/12/14
///	@desc	Undo/Redo op.

#pragma once

#include "xeres.h"

namespace xeres {

	/*!
		\class	CmdOp
		\brief	Undo/Redo op interface.
	*/
	class CmdOp
	{
	public:

		/// \ctor
		CmdOp( void ) {}

		/// \dtor
		virtual ~CmdOp( void ) {}

	public:
		// redo
		virtual bool Redo( void * data ) = 0;
		// undo
		virtual bool Undo( void * data ) = 0;

		// can undo
		virtual bool CanUndo( void ) { return true; }

		// flush ( return true demand delete op )
		virtual bool Flush( void ) { return true; }

	};

	/*!
		\desc

		The layout of CmdOpStack is likely

		-----------------------------
		|  undo	|					|
		0-------^-------------------size
		|		|
	start	 current->

		-----------------------------
		|		undo				|
		0---------------------------size
		|							|
	start						current->

	start->
		-|---------------------------
		||  						|
	0+---------------------------size
		|
	current->

		-----------------------------
		|  undo	| redo	|			|
		0-------^-------^-----------size
				|		|
			<-current  start
	*/

	/*!
		\class CmdOpStack
		\brief command op stack.
	*/
	class CmdOpStack
	{
	public:
		/// \ctor
		CmdOpStack( int stack_size ) : m_OpSize(stack_size) , m_OpCurrent(0) , m_OpStart(0) {
			m_OpStack = (CmdOp**) malloc( sizeof( CmdOp * ) * stack_size );
			memset( m_OpStack , 0 , sizeof( CmdOp * ) * stack_size );
		}
		/// \dtor
		~CmdOpStack( void ) {
			ClearAll();
			free( m_OpStack );
		}

		// resize
		void Resize( int size ) {
			if( size == m_OpSize )
				return;
			CmdOp** pNew = (CmdOp**)malloc( sizeof( CmdOp * ) * size );
			memset( m_OpStack , 0 , sizeof( CmdOp * ) * size );
			if( m_OpStart != m_OpCurrent )
			{
				if( size < m_OpSize )
				{// cut off tail
					int i , p;
					for( i = 0 , p = m_OpStart ; i < m_OpSize - size ; ++i )
					{
						p = GetNext(p);
						assert( m_OpStack[p] );
						if( m_OpStack[p]->Flush() )
							delete m_OpStack[p];
					}
					m_OpStart = p;
				}
				m_OpStart = GetNext( m_OpStart );
				CmdOp * op = m_OpStack[m_OpStart];
				int s_current = 0;
				while( op )
				{
					++s_current;
					pNew[s_current] = op;
					op = m_OpStack[m_OpCurrent];
				}
				m_OpCurrent = s_current;
			}
			else
			{
				m_OpCurrent = 0;
			}
			m_OpStack = 0;
			m_OpSize = size;
			free( m_OpStack );
			m_OpStack = pNew;
		}

		// Add Op
		bool AddOp( CmdOp * op , void * data ) {
			if( !op->Redo( data ) )
				return false;
			AddOp( op );
			return true;
		}

		// Add op
		void AddOp( CmdOp * op ) {
			m_OpCurrent = GetNext( m_OpCurrent );
			// test if before START or NULL
			if( m_OpCurrent == m_OpStart )
			{// reach the bottom of queue, flush one
				int p = GetNext( m_OpStart );
				assert( m_OpStack[p] );
				if( m_OpStack[p]->Flush() )
					delete m_OpStack[p];
				m_OpStack[p] = NULL;
				// increase start tag
				m_OpStart = p;
				m_OpStack[m_OpCurrent] = op;
			}
			else if( m_OpStack[m_OpCurrent] == NULL )
			{// add directly
				m_OpStack[m_OpCurrent] = op;
			}
			else
			{// with some redo-op, flush them
				int p = m_OpCurrent;
				// clear Redo ready Op
				while( p != m_OpStart && m_OpStack[p] )
				{
					if( m_OpStack[p]->Flush() )
						delete m_OpStack[p];
					m_OpStack[p] = NULL;
					p = GetNext( p );
				}
				m_OpStack[m_OpCurrent] = op;
			}
		}

		// query
		bool CanUndo( void ) const {
			return m_OpCurrent != m_OpStart;
		}
		bool CanRedo( void ) const {
			int p = GetNext( m_OpCurrent );
			if( p == m_OpStart )
				return false;
			if( m_OpStack[p] == NULL )
				return false;
			return true;
		}

		// Undo
		bool Undo( void * data ) {
			if( m_OpCurrent == m_OpStart )
				return false;
			bool res = m_OpStack[m_OpCurrent]->Undo( data );
			m_OpCurrent = GetPrev( m_OpCurrent );
			return res;
		}

		// redo
		bool Redo( void * data ) {
			int p = GetNext( m_OpCurrent );
			if( p == m_OpStart )
				return false;
			if( m_OpStack[p] == NULL )
				return false;
			bool res = m_OpStack[p]->Redo( data );
			m_OpCurrent = p;
			return res;
		}

		// clear all
		void ClearAll( void ) {
			int p = m_OpCurrent;
			// flush redo
			if( m_OpStack[p] && p != m_OpStart )
			{
				while( p != m_OpStart && m_OpStack[p] )
				{
					CmdOp * op = m_OpStack[p];
					if( op->Flush() )
						delete op;
					m_OpStack[p] = NULL;
					p = GetNext( p );
				}
			}
			// flush undo
			p = GetPrev( m_OpCurrent );
			while( p != m_OpStart && m_OpStack[p] )
			{
				if( m_OpStack[p]->Flush() )
					delete m_OpStack[p];
				m_OpStack[p] = NULL;
				p = GetPrev( p );
			}
			// rewind
			m_OpStart = 0;
		}

	private:

		// Get next
		int GetNext( int current ) const {
			return ( ++current >= m_OpSize ) ? 0 : current;
		}
		// Get previous
		int GetPrev( int current ) const {
			return ( --current < 0 ) ? m_OpSize - 1 : current;
		}
	
	//@ data
		CmdOp **		m_OpStack;
		int				m_OpSize;
		int				m_OpCurrent;
		int				m_OpStart;

	};

} // namespace xeres
